home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 May: Tool Chest / Developer CD Series May 1996 (Tool Chest) (Apple Computer) (1996).iso / Tool Chest / Development Tools & Languages / Macintosh Common Lisp Related / User Contributions / dm Folder.sea / dm Folder / dungeon-master.lisp < prev   
Encoding:
Text File  |  1993-02-12  |  37.0 KB  |  1,112 lines  |  [TEXT/CCL2]

  1. ;;; -*- Mode: Lisp; Package: DUNGEON-MASTER; -*-
  2.  
  3. ;;; $Header: /afs/athena.mit.edu/course/other/allp/nlp/tools/RCS/dungeon-master.lisp,v 3.2 92/07/08 16:29:09 sfelshin Exp $
  4.  
  5. ;;; ================================================================
  6. ;;;
  7. ;;;                The Dungeon Master
  8. ;;;
  9. ;;;               An alternative module system
  10. ;;;
  11. ;;;                Version 1.0
  12. ;;;
  13. ;;;                Source code
  14. ;;;
  15. ;;; ================================================================
  16.  
  17. ;;; Copyright (c) 1986-90, 1992-93 Massachusetts Institute of Technology.
  18. ;;; Permission is granted to use this software and to pass on copies,
  19. ;;; provided that this copyright notice is retained intact.  In addition,
  20. ;;; permission is granted to modify this software, provided all changes are
  21. ;;; prominently marked.  For more details, refer to the accompanying file
  22. ;;; "dm-copyright.text".
  23.  
  24. ;;; Brought to you by the Laboratory for Advanced Technology in the
  25. ;;; Humantities (formerly the Athena Language Learning Project) at MIT.
  26. ;;; "Bigger!  Better!  Slower!"
  27.  
  28. ;;; Software by Sue Felshin and Stuart Malone.  Documentation by Sue
  29. ;;; Felshin.
  30.  
  31. ;;; The Dungeon Master system consists of six files:
  32. ;;;   1) source code ("dungeon-master.lisp"),
  33. ;;;   2) a detailed copyright notice ("dm-copyright.text"),
  34. ;;;   3) user documentation in mss (Scribe) format ("dm.mss"),
  35. ;;;   4) PostScript format ("dm.text"), and
  36. ;;;   5) text format, and
  37. ;;;   6) examples of using the Dungeon Master ("dm-use.lisp").
  38. ;;; This is file number 1.
  39.  
  40. ;;; If you improve the Dungeon Master, you are encouraged to mail changes
  41. ;;; to sfelshin@athena.mit.edu.
  42.  
  43. ;;; This software has been tested with Lucid Common Lisp 4.0.0 and MCL
  44. ;;; 2.0f.  It should also work with MCL 1.[whatever the last version was].
  45. ;;; It will _not_ work with MCL2.0b unless you modify the code to use the
  46. ;;; public domain pretty printer.  A slightly older version of this code
  47. ;;; was tested with version of Lucid (1.01) on RTs.
  48.  
  49. ;;; This file has three parts.  First, it defines a new package and defines
  50. ;;; some extensions to Common Lisp's file system interface.  Then it
  51. ;;; defines some code to make it easier to deal with mutually-dependent
  52. ;;; packages, and ensures that the pretty printer and condition system are
  53. ;;; available if needed, even in non-fully-CLtL2-compatible Common Lisps.
  54. ;;; Finally, it defines the Dungeon Master, an extended module system.  The
  55. ;;; Dungeon Master is an extension of the module system described in
  56. ;;; Section 11.8 of CLtL1, but is incompatible with that system to the
  57. ;;; extent that it requires a one-to-one correspondence between modules and
  58. ;;; files.
  59.  
  60. ;;; This file defines a module system.  It is not itself a module.
  61.  
  62. ;;; To configure the Dungeon Master for an as-yet-unsupported
  63. ;;; configuration, search for calls to CONFIGURE-ME-ERROR in this file.
  64. ;;; Add the appropriate code for your lisp.  In some cases this will
  65. ;;; consist merely of adding your lisp's distinguishing feature(s) to an
  66. ;;; existing #+ form and subtracting them from the associated error form.
  67. ;;; In other cases, you will have to add a small amount of code.  After you
  68. ;;; have configured your lisp, please send your changes to
  69. ;;; sfelshin@athena.mit.edu so that they can be integrated into the
  70. ;;; official cody of the Dungeon Master.
  71.  
  72.  
  73. ;;; ================================================================
  74. ;;;
  75. ;;;                Part the First.
  76. ;;;
  77. ;;;
  78. ;;;        In which we create a new package and extend
  79. ;;;           Common Lisp's file system interface.
  80. ;;;
  81. ;;; ================================================================
  82.  
  83. ;;; This code was written in the days of CLtL1, before Common Lisp had
  84. ;;; specifications for case conversion, structured directories, extended
  85. ;;; wildcards, and logical pathnames.  Much of the code in this section
  86. ;;; could be eliminated by making use of these new features of CLtL2.
  87.  
  88.  
  89. ;;; Many lisps will complain, upon loading this file, that the file does
  90. ;;; not begin with an IN-PACKAGE statement.  This is quite proper.  After
  91. ;;; we have defined a package, we will switch to it.
  92.  
  93.  
  94. ;;; ================================
  95. ;;; Define a new package because is an error to add external symbols to the
  96. ;;; Common Lisp package.
  97.  
  98. (eval-when (eval load compile)
  99.   (or (find-package :dm)
  100.       (make-package :dm
  101.     ;; CLtL2, p 263: "... the default value for the :USE argument is
  102.     ;; unspecified.  Portable code should specify :USE '("COMMON-LISP")
  103.     ;; explicitly.
  104.     :use '(#-lucid "COMMON-LISP"
  105.            ;; Lucid Common Lisp is not 100% CLtL2 compatible yet.
  106.            #+lucid "LISP" #+lucid "LUCID-COMMON-LISP"))))
  107.  
  108. (in-package :dm)
  109.  
  110. (eval-when (eval load compile)
  111.   (export '(search-for-file find-module-file push-module-search-path
  112.         mload mcomp load-modules compile-modules
  113.         provide-module require-module unprovide-module
  114.         mload-protect uncompiled-modules modules-dependent-on)
  115.       :allp))
  116.  
  117.  
  118. ;;; =====================================
  119. ;;; Define some useful pathname functions.
  120.  
  121. (defun configure-me-error ()
  122.   (error "The Dungeon Master needs to be configured for this Lisp."))
  123.  
  124. ;;; Add the AIX feature to Lucid on the RT under AIX, which normally doesn't 
  125. ;;; have one.
  126. #+(and lucid (not rios) (not ultrix) (not ps2)) (pushnew :aix *features*)
  127.  
  128. (defparameter *lsp-type*
  129.   #+(and lucid aix (not rios)) "l"
  130.   #+(and lucid rios) "lisp"
  131.   #+:ccl "lisp"
  132.   #+ultrix "lisp"
  133.   #+(not (or (and lucid aix (not rios)) (and lucid rios) :ccl ultrix))
  134.   (configure-me-error))
  135.   
  136. (defparameter *fas-type*
  137.   #+(and lucid aix (not rios)) "b"
  138.   #+(and lucid rios lcl4.0) "rbin"
  139.   #+(and lucid ps2) "3bin"        ;Really should check version of Lucid?
  140.   #+:ccl "fasl"
  141.   #+ultrix "mbin"
  142.   #-(or (and lucid aix (not rios)) (and lucid rios) :ccl ultrix)
  143.   (configure-me-error))
  144.  
  145. (defun push-directory (dir path)
  146.   (make-pathname
  147.     :directory
  148.       #+lucid (append (pathname-directory path) (list (string-downcase dir)))
  149.       #+:ccl-2 (append (pathname-directory path) (list dir))
  150.       #+(and :ccl (not :ccl-2)) (concatenate 'string (pathname-directory path)
  151.                          (string dir) ":")
  152.       #-(or lucid :ccl) (configure-me-error)
  153.     :defaults path))
  154.  
  155. (defun pop-directory (path)
  156.   (make-pathname
  157.     :directory
  158.       #+(or lucid ccl-2) (butlast (pathname-directory path))
  159.       #+(and :ccl (not :ccl-2)) (subseq (pathname-directory path) 0 
  160.              (position #\: (pathname-directory path) :from-end))
  161.       #-(or lucid :ccl) (configure-me-error)
  162.     :defaults path))
  163.  
  164. (defun canonical-pathname (path)
  165.   #+(and lucid aix (not rios) (not ultrix))
  166.   (let ((name (pathname-name path)))
  167.     (cond ((stringp name)
  168.        (when (> (length name) 11)
  169.          (setq name (subseq name 0 11)))
  170.        (when (some #'upper-case-p name)
  171.          (setq name (string-downcase name)))
  172.        (make-pathname :name name :defaults path))
  173.       (t path)))
  174.   #+(and lucid (or rios ultrix))
  175.   (let ((name (pathname-name path)))
  176.     (cond ((stringp name)
  177.        (when (some #'upper-case-p name)
  178.          (setq name (string-downcase name)))
  179.        (make-pathname :name name :defaults path))
  180.       (t path)))
  181.   #+ccl-2
  182.   (let ((name (pathname-name path)))
  183.     (cond ((stringp name)
  184.        (when (> (length name) 26)    ;31 - 5 for dot and extension
  185.          (setq name (subseq name 0 26)))
  186.        (make-pathname :name name :defaults path))
  187.       (t path)))
  188.   #+(not (or ccl-2
  189.          (and lucid (or rios ultrix))
  190.          (and lucid aix (not rios) (not ultrix))))
  191.   (configure-me-error))
  192.  
  193.  
  194.  
  195.  
  196. ;;; ================================================================
  197. ;;;
  198. ;;;                Part the Second.
  199. ;;;
  200. ;;;
  201. ;;;      In which we define some code to make it easier to deal
  202. ;;;       with multiple, possibly mutually-dependent packages.
  203. ;;;
  204. ;;;
  205. ;;; ================================================================
  206.  
  207.  
  208. ;;; We use the pretty printer and condition system as described in CLtL2.
  209. ;;; These modules are not yet part of all Common Lisps, so install the
  210. ;;; public versions when necessary, and make them accessible via these two
  211. ;;; macros.
  212.  
  213. ;;; Lisps with built-in pretty printers.
  214. #+ccl nil
  215.  
  216. ;;; Lisps without built-in pretty printers.
  217. #+(or lucid (and :ccl (not ccl-2)))
  218. (when (not (and (find-package "XP") (find-symbol "INSTALL" "XP")))
  219.   (load (make-pathname
  220.      :name "xp"
  221.      :defaults (make-pathname :name nil :type nil
  222.                   :defaults *load-pathname*))))
  223.  
  224. ;;; Unconfigured lisps.
  225. #-(or lucid :ccl) (configure-me-error)
  226.  
  227. (defmacro use-pretty-printer ()
  228.   `(eval-when (:execute :load-toplevel :compile-toplevel)
  229.      #+(or lucid (and :ccl (not ccl-2)))
  230.      (eval `(,(find-symbol "INSTALL" "XP") :macro t))
  231.      #+ccl-2 nil
  232.      #-(or lucid :ccl) (configure-me-error)))
  233.  
  234. ;;; Lisps with built-in condition systems.
  235. #+(or lcl4.0 ccl-2) nil
  236.  
  237. ;;; Lisps without built-in condition systems.
  238. #+(or (and lucid (not lcl4.0))
  239.       (and :ccl (not :ccl-2)))
  240. (load (make-pathname
  241.      :name "conditions"
  242.      :defaults (make-pathname :name nil :type nil
  243.                   :defaults *load-pathname*)))
  244.  
  245. ;;; Unconfigured lisps.
  246. #-(or lucid :ccl) (configure-me error)
  247.  
  248. (defmacro use-condition-system ()
  249.   `(eval-when (eval load compile)
  250.      #+(or lcl4.0 ccl-2) nil
  251.  
  252.      ;; Two step process.  I don't know of any lisps that have a built-in
  253.      ;; pretty but no built-in condition system.
  254.      #+(or (and lucid (not lcl4.0))
  255.        (and :ccl (not :ccl-2)))
  256.      (eval `(,(find-symbol "INSTALL" "XP") :macro t))
  257.      #+(or (and lucid (not lcl4.0))
  258.        (and :ccl (not :ccl-2)))
  259.      (eval `(,(find-symbol "INSTALL" "CONDITIONS")))
  260.  
  261.      #-(or lucid :ccl) (configure-me-error)
  262.     ))
  263.  
  264.  
  265. ;;; Packages every package wants to use.
  266. (defparameter *base-packages*
  267.   #+lucid '("LISP" "LUCID-COMMON-LISP" "DM")
  268.   #-lucid '("COMMON-LISP" "DM"))
  269.  
  270. ;;; Make a symbol accessible to _every_ package that uses package DM.
  271. ;;; Can be useful for debugging extensions.  It is tasteless to use this
  272. ;;; for code which is called non-interactively.
  273. (defun global (syms)
  274.   (unless (listp syms) (setq syms (list syms)))
  275.   (dolist (sym syms)
  276.     (import sym :dm)
  277.     (export sym :dm))
  278.   t)
  279.  
  280. ;;; Define possibly mutually dependent packages.  This macro tears multiple
  281. ;;; package definitions apart and puts them back together again sideways.
  282. ;;; It arranges definitions so that first all packages are created, then
  283. ;;; the pretty printer and condition are made accessible to all packages as
  284. ;;; necessary, then symbols are shadowed in all packages, etc., etc., etc.
  285. (defmacro define-packages (&rest pkg-defs)
  286.   (labels ((intern-forms (strings package)
  287.          (mapcar #'(lambda (string)
  288.              `(intern ,string ,package))
  289.              strings)))
  290.     (do* ((defs pkg-defs (rest defs))
  291.       (def (first defs) (first defs))
  292.       (name (cadr (assoc :name def)) (cadr (assoc :name def)))
  293.       (nicknames (rest (assoc :nicknames def))
  294.              (rest (assoc :nicknames def)))
  295.       (shadows (rest (assoc :shadow def))
  296.            (rest (assoc :shadow def)))
  297.       (shadowing-imports (rest (assoc :shadowing-import def))
  298.                  (rest (assoc :shadowing-import def)))
  299.       (exports (rest (assoc :export def))
  300.            (rest (assoc :export def)))
  301.       (globals (rest (assoc :global def))
  302.            (rest (assoc :global def)))
  303.       (uses (rest (assoc :use def))
  304.         (rest (assoc :use def)))
  305.       (imports (rest (assoc :import def))
  306.            (rest (assoc :import def)))
  307.       (make-forms nil)
  308.       (xp-forms nil)
  309.       (conditions-forms nil)
  310.       (shadow-forms nil)
  311.       (shadowing-import-forms nil)
  312.       (export-forms nil)
  313.       (global-forms nil)
  314.       (use-forms nil)
  315.       (import-forms nil)
  316.       (warning-forms nil))
  317.      ((endp defs)
  318.       `(#-lucid progn
  319.         ;; Lucid seems to evaluate calls to shadow at compile time,
  320.         ;; even though SHADOW is not one of the functions listed on pp.
  321.         ;; 685-691 as being required to be evaluated at compile time.
  322.         #+lucid eval-when #+lucid (load eval)
  323.         ,@make-forms
  324.         ,@xp-forms
  325.         ,@conditions-forms
  326.         ,@shadow-forms
  327.         ,@shadowing-import-forms
  328.         ,@export-forms
  329.         ,@global-forms
  330.         ,@use-forms
  331.         ,@import-forms
  332.         ,@warning-forms))
  333.       (push `(unless (find-package ,name)
  334.            (make-package ,name
  335.         :use ',*base-packages*
  336.         ,@(when nicknames `(:nicknames ',nicknames))))
  337.         make-forms)
  338.       #+(or lucid ccl)
  339.       (when (cadr (assoc :pretty-printing def))
  340.     (push `(let ((*package* (find-package ,name)))
  341.          (funcall (find-symbol "INSTALL" "XP") :macro t))
  342.           xp-forms))
  343.       #-(or lcl4.0 :ccl-2)
  344.       (when (cadr (assoc :conditions def))
  345.     (push `(let ((*package* (find-package ,name)))
  346.          (funcall (find-symbol "INSTALL" "CONDITIONS") :macro t))
  347.           conditions-forms))
  348.       (when shadows
  349.     (push `(shadow (list ,@(intern-forms shadows name)) ,name)
  350.           shadow-forms))
  351.       (when shadowing-imports
  352.     (push `(shadowing-import
  353.          (list ,@(mapcar
  354.               #'(lambda (import-form)
  355.                   `(find-symbol ,(first import-form)
  356.                 ,(second import-form)))
  357.               shadowing-imports))
  358.          ,name)
  359.           shadowing-import-forms))
  360.       (when exports
  361.     (push `(export (list ,@(intern-forms exports name)) ,name)
  362.           export-forms))
  363.       (when globals
  364.     (push `(let ((*package* (find-package ,name)))
  365.          (funcall (find-symbol "GLOBAL" "ALLP")
  366.               (list ,@(intern-forms globals name))))
  367.           global-forms))
  368.       (when uses
  369.     (push `(use-package ',uses ,name) use-forms))
  370.       (when imports
  371.     (push `(import (list ,@(mapcar
  372.                 #'(lambda (import-form)
  373.                     `(find-symbol ,(first import-form)
  374.                           ,(second import-form)))
  375.                 imports))
  376.                ,name)
  377.           import-forms))
  378.       (dolist (form def)
  379.     (unless (member (car form)
  380.             '(:name :nicknames :shadow :shadowing-import :export
  381.               :global :use :import :pretty-printing :conditions))
  382.       (push `(warn "Unknown keyword ~S in form ~S" ,(car form) ',form)
  383.         warning-forms))))))
  384.  
  385. #||
  386.  
  387.  
  388.  
  389. ;;; ================================================================
  390. ;;;
  391. ;;;                Part the Third.
  392. ;;;
  393. ;;;
  394. ;;;          In which we define the Dungeon Master.
  395. ;;;
  396. ;;;
  397. ;;; ================================================================
  398.  
  399. ;;; The Dungeon Master is an extension of the module system described in
  400. ;;; Section 11.8 of CLtL1, but is incompatible with that system to the
  401. ;;; extent that it requires a one-to-one correspondence between modules and
  402. ;;; files.
  403.  
  404.  
  405. ;;; ================================================================
  406. ;;; The module search path.
  407.  
  408. (defvar *module-search-path* ())
  409.  
  410. (defun push-module-search-path (path)
  411.   (setq path (pathname path))
  412.   (when (pathname-name path)
  413.     (cerror "Push ~A onto the search path anyway."
  414.         "You probably don't want to push ~A
  415. onto the module search path." (namestring path)))
  416.   (pushnew path *module-search-path* :test #'equal))
  417.  
  418.  
  419. ;;; ================================================================
  420. ;;; Implementation-specific variables.
  421.  
  422. ;;; For each implementation of Common Lisp, classify types of code
  423. ;;; according to types of dependency.  To load, compile, or execute code,
  424. ;;; it may be necessary to load or compile code which that code depends on,
  425. ;;; or to arrange for it to be loaded eventually.
  426.  
  427. ;;; Actually, dependency types may vary within a lisp according to the
  428. ;;; current values of the qualities of the OPTIMIZE declaration.  However,
  429. ;;; since it is not possible to find out under what values a module was
  430. ;;; compiled, one must always assume the worst (= most preloading,
  431. ;;; reloading, precompiling, and recompiling).
  432.  
  433. ;;; To configure a new lisp/architecture combination, refer to your
  434. ;;; configuration's documentation to discern what code dependencies it
  435. ;;; creates, or experiment with loading dependent code samples, and with
  436. ;;; compiling and disassembling them.
  437.  
  438. (defstruct (dm-keyword
  439.          (:conc-name dmk-)
  440.          (:constructor
  441.           dmk
  442.           (requires-preloading requires-precompiling incorporates-code
  443.                    &rest keywords)))
  444.   (keywords            ()    :type list)
  445.   ;; Means "requires preloading in order to be loaded".
  446.   (requires-preloading        nil    :type (member nil t))
  447.   ;; Means "requires precompiling in order to be compiled inline".
  448.   (requires-precompiling    nil    :type (member nil t))
  449.   ;; E.g., macros and inline stuff.  Implies the previous two, plus
  450.   ;; "requires reloading of dependent modules".
  451.   (incorporates-code        nil    :type (member nil t)))
  452.  
  453. ;;; The values given below were calculated for lisps compiling using the
  454. ;;; default OPTIMIZE qualities, NOT the worst-case (tightest code)
  455. ;;; optimization qualities.  Oops.
  456.  
  457. #+lucid
  458. (defparameter *dm-keywords*
  459.   (list
  460.    (dmk nil  t   t  :constructors :accessors)
  461.    (dmk  t   t   t  :modifiers)
  462.    (dmk nil  t   t  :inline :inlines)
  463.    (dmk  t  nil  t  :macros)
  464.    (dmk  t  nil nil :toplevel-calls)
  465.    (dmk  t  nil  t  :includes)        ; Includes structs in other structs.
  466.    (dmk  t  nil nil :classes)        ; Uses classes as superclasses.
  467.    (dmk  t  nil nil :specializers)    ; Uses types as specializers.
  468.    (dmk  t  nil nil :types)        ; Declares types defined elsewhere
  469.    (dmk  t  nil  t  :read-macros :reader-macros)))
  470.  
  471. #+vaxlisp
  472. (defparameter *dm-keywords*
  473.   (list
  474.    (dmk nil  t   t  :accessors :modifiers)
  475.    (dmk nil nil nil :constructors)
  476.    (dmk nil  t   t  :inline :inlines)
  477.    (dmk  t  nil  t  :macros)
  478.    (dmk  t  nil nil :toplevel-calls)
  479.    (dmk  t  nil  t  :includes)
  480.    (dmk  t  nil nil :classes)
  481.    (dmk  t  nil nil :specializers)
  482.    (dmk  t  nil nil :types)        ; Declares types defined elsewhere
  483.    (dmk  t  nil  t  :read-macros :reader-macros)))
  484.  
  485. #+:ccl
  486. (defparameter *dm-keywords*
  487.   (list
  488.    (dmk nil nil  t  :accessors :modifiers)
  489.    (dmk nil nil nil :constructors)
  490.    (dmk nil nil nil :inline :inlines)
  491.    (dmk  t  nil  t  :macros)
  492.    (dmk  t  nil nil :toplevel-calls)
  493.    (dmk  t  nil  t  :includes)
  494.    (dmk  t  nil nil :classes)
  495.    (dmk  t  nil nil :specializers)
  496.    (dmk  t  nil nil :types)        ; Declares types defined elsewhere
  497.    (dmk  t  nil  t  :read-macros :reader-macros)))
  498.  
  499. #-(or lucid vaxlisp :ccl)
  500. (configure-me-error)
  501.  
  502. (defun valid-keyword-p (keyword)
  503.   (dolist (dmk *dm-keywords* t)
  504.     (when (member keyword (dmk-keywords dmk))
  505.       (return t))))
  506.    
  507. (defun keywords-cross-p (keywords dmk)
  508.   (declare (list keywords) (type dm-keyword dmk))
  509.   (dolist (keyword keywords nil)
  510.     (declare (keyword keyword))
  511.     (dolist (dmk-keyword (dmk-keywords dmk))
  512.       (declare (keyword dmk-keyword))
  513.       (when (eq keyword dmk-keyword)
  514.     (return-from keywords-cross-p t)))))
  515.  
  516. (defun incorporates-code-p (keywords)
  517.   (declare (list keywords))
  518.   (dolist (dmk *dm-keywords* nil)
  519.     (declare (type dm-keyword dmk))
  520.     (when (and (dmk-incorporates-code dmk)
  521.            (keywords-cross-p keywords dmk))
  522.       (return t))))
  523.  
  524. (defun requires-preloading-p (keywords)
  525.   (declare (list keywords))
  526.   (dolist (dmk *dm-keywords* nil)
  527.     (declare (type dm-keyword dmk))
  528.     (when (and (dmk-requires-preloading dmk)
  529.            (keywords-cross-p keywords dmk))
  530.       (return t))))
  531.  
  532. (defun requires-precompiling-p (keywords)
  533.   (declare (list keywords))
  534.   (dolist (dmk *dm-keywords* nil)
  535.     (declare (type dm-keyword dmk))
  536.     (when (and (dmk-requires-precompiling dmk)
  537.            (keywords-cross-p keywords dmk))
  538.       (return t))))
  539.  
  540.  
  541. ;;; ================================================================
  542. ;;; Information about files.
  543.        
  544. (defvar *empty-pathname* (make-pathname))
  545.  
  546. (defun file-compilation-status (file)
  547.   (let ((lsp-file (make-pathname :type *lsp-type* :defaults file))
  548.     (fas-file (make-pathname :type *fas-type* :defaults file)))
  549.     (declare (pathname lsp-file fas-file))
  550.     (let ((lsp-date (file-write-date lsp-file))
  551.       (fas-date (file-write-date fas-file)))
  552.       (declare (type (or null integer) lsp-date fas-date))
  553.       (cond ((not lsp-date)
  554.          (cond ((not fas-date)
  555.             (values nil nil nil))
  556.            (t
  557.             (values :fas-only nil fas-file))))
  558.         ((not fas-date)
  559.          (values :lsp-only lsp-file nil))
  560.         ((>= fas-date lsp-date)
  561.          (values :fas-most-recent lsp-file fas-file))
  562.         (t
  563.          (values :lsp-most-recent lsp-file fas-file))))))
  564.  
  565. (defun find-appropriate-version (file)
  566.   (cond ((pathname-type file)
  567.      (probe-file file))
  568.     (t
  569.      (multiple-value-bind (status lsp-file fas-file)
  570.          (file-compilation-status file)
  571.        (if status
  572.            (if (or (eq status :lsp-only) (eq status :lsp-most-recent))
  573.            lsp-file
  574.          fas-file))))))
  575.  
  576. (defun search-for-file (file &optional (search-path *module-search-path*))
  577.   (dolist (library search-path
  578.            (find-appropriate-version
  579.             (merge-pathnames file *default-pathname-defaults*)))
  580.     (let ((it (find-appropriate-version (merge-pathnames file library))))
  581.       (if it (return it)))))
  582.  
  583.  
  584. ;;;============================================================================
  585. ;;; Information about modules.
  586. ;;;============================================================================
  587.  
  588. (defvar *module-table* (make-hash-table :test 'equal))
  589. (defvar *modules-being-loaded* ())
  590.  
  591. (defstruct (module-info
  592.          (:conc-name module-)
  593.          (:constructor make-module (name))
  594.          (:print-function print-module))
  595.   (name            ""    :type string)
  596.   ;; File loaded for that module, or NIL if not loaded
  597.   (file            nil    :type (or null pathname))
  598.   ;; Time when last load took place; if zero, module hasn't been successfully
  599.   ;; loaded yet.
  600.   (load-time        0    :type integer)
  601.   ;; Whether or not the file should be compiled
  602.   (compile-p        t    :type symbol)
  603.   ;; Documentation string
  604.   (doc            nil    :type (or null string))
  605.   ;; Modules that this module is dependent on
  606.   (dependencies        ()    :type list)
  607.   )
  608.  
  609. (defun print-module (m &optional (s *standard-output*) l)
  610.   (declare (ignore l))
  611.   (format s "#<MODULE ~A>" (module-name m)))
  612.  
  613. (defun module (name)
  614.   (cond ((module-info-p name)
  615.      name)
  616.     (t
  617.      (setq name (string name))
  618.      (or (gethash name *module-table*)
  619.          (setf (gethash name *module-table*)
  620.            (make-module name))))))
  621.  
  622. (defun module-loading-p (module)
  623.   (member module *modules-being-loaded*))
  624.  
  625. (defun module-total-dependencies (module)
  626.   (setq module (module module))
  627.   (let ((result ()))
  628.     (labels ((add-dependency (m)
  629.            (unless (member m result)
  630.          (push m result)
  631.          (dolist (child (module-dependencies m))
  632.            (add-dependency child)))))
  633.       (dolist (d (module-dependencies module))
  634.     (add-dependency d)))
  635.     result))
  636.  
  637.  
  638. ;;;============================================================================
  639. ;;; Mapping modules to files.
  640. ;;;============================================================================
  641.  
  642. (defun find-module-file (module &optional file lsp-only (error-p t))
  643.  
  644.   (setq module (module module))
  645.   (setq file (if file (pathname file) *empty-pathname*))
  646.  
  647.   ;; If the file has been loaded before, then push its pathname onto the search
  648.   ;; path so that it will be tried first.
  649.   (do ((search-path
  650.     (if (module-file module)
  651.         (cons (make-pathname
  652.            :name (pathname-name (module-file module))
  653.            :directory (pathname-directory (module-file module))
  654.            :type nil
  655.            :version nil)
  656.           *module-search-path*)
  657.       *module-search-path*))
  658.        result)
  659.       (())
  660.  
  661.     ;; This goes inside the loop so that when a file to be compiled isn't found
  662.     ;; and the user specifies a new name, only lisp files will be considered.
  663.     (when lsp-only
  664.       (setq file (make-pathname :type *lsp-type* :defaults file)))
  665.  
  666.     ;; If the file doesn't have a filename yet, default to the name of the
  667.     ;; module.
  668.     (when (or (not (pathname-name file))
  669.           (and (stringp (pathname-name file))
  670.            (= (length (pathname-name file)) 0)))
  671.       (setq file
  672.         (canonical-pathname
  673.           (make-pathname :name (module-name module)
  674.                  :defaults file))))
  675.  
  676.     ;; Look for the file, and return it if found.
  677.     (when (setq result (search-for-file file search-path))
  678.       (return result))
  679.  
  680.     ;; Ask for a new filename, or return NIL if the user wants us to.
  681.     (cond (error-p
  682.        (cerror "Enter a new file name, or ignore the file."
  683.            "File not found: ~A" (namestring file))
  684.        (format *query-io* "New filename (return to ignore): ")
  685.        (when (zerop (length (setq file (read-line *query-io*))))
  686.          (return nil)))
  687.       (t
  688.        (return nil)))))
  689.  
  690. (defun uncompiled-modules ()
  691.   (let ((result ()))
  692.     (maphash #'(lambda (module-name module)
  693.          (when (and (module-file module)
  694.                 (equal *lsp-type*
  695.                    (pathname-type (module-file module))))
  696.            (push module-name result)))
  697.          *module-table*)
  698.     result))
  699.  
  700. (defun intersects-p (a b)
  701.   (declare (list a b))
  702.   (dolist (a-item a nil)
  703.     (when (member a-item b)
  704.       (return t))))
  705.  
  706. (defun modules-dependent-on (&rest modules)
  707.   (setq modules (mapcar #'module modules))
  708.   (let ((result ()))
  709.     (maphash #'(lambda (module-name module)
  710.          (declare (ignore module-name))
  711.          (when (intersects-p modules
  712.                      (module-total-dependencies module))
  713.            (push module result)))
  714.          *module-table*)
  715.     result))
  716.  
  717. ;;;============================================================================
  718. ;;; Compiling files
  719. ;;;============================================================================
  720.  
  721. ;;; The following goofiness handles recursive compiles.
  722.  
  723. (defvar *dm-files-being-compiled* nil)
  724.  
  725. (defun dm-compile-file (file)
  726.   (cond (*dm-files-being-compiled*
  727.      (unless
  728.          (member file *dm-files-being-compiled* :test #'equal)
  729.        (format t "~&DM: aborting compilation of ~A
  730.     in order to compile ~A.~%"
  731.            (namestring (first *dm-files-being-compiled*))
  732.            (namestring file))
  733.        (push file *dm-files-being-compiled*)
  734.        (throw :precompile file)))
  735.     (t
  736.      (do ((*dm-files-being-compiled* (list file)))
  737.          ((endp *dm-files-being-compiled*) t)
  738.        (catch :precompile
  739.          (when (catch :abort-compilation
  740.              (let ((*package* (find-package "ALLP")))
  741.                (compile-file (first *dm-files-being-compiled*)))
  742.              nil)
  743.            (warn "Compilation of ~A aborted."
  744.              (namestring (first *dm-files-being-compiled*))))
  745.          (pop *dm-files-being-compiled*))))))
  746.  
  747.  
  748. ;;;============================================================================
  749. ;;; Querying the user
  750. ;;;============================================================================
  751.  
  752. (defun query (choices format-string &rest format-args)
  753.   (loop
  754.    (fresh-line *query-io*)
  755.    (apply #'format *query-io* format-string format-args)
  756.    (format *query-io* " ~A " (mapcar #'second choices))
  757.    (finish-output *query-io*)
  758.    (let ((answer (read-one-char *query-io*)))
  759.      (dolist (choice choices)
  760.        (when (find answer (rest choice) :test #'char=)
  761.      (return-from query (first choice)))))))
  762.  
  763. ;;;============================================================================
  764. ;;; Loading and compiling individual modules
  765. ;;;============================================================================
  766.  
  767. (defvar *current-module* nil)
  768. (defvar *module-provided-p* nil)
  769. (defvar *unwind-protect*)
  770.  
  771. (defun module-needs-compiling-p (module path)
  772.   (setq module (module module))
  773.   (let ((file (find-module-file module path t)))
  774.     (when file
  775.       (multiple-value-bind (status lsp-file fas-file)
  776.           (file-compilation-status file)
  777.         (case status
  778.           ((:lsp-only :lsp-most-recent)
  779.        (format *query-io* "~2&")
  780.        lsp-file)
  781.       (:fas-most-recent
  782.        (let ((changed-modules ()))
  783.          (dolist (depend-mod (module-total-dependencies module))
  784.            (let ((depend-file (find-module-file depend-mod nil t)))
  785.          (when (and depend-file
  786.                 (> (file-write-date depend-file)
  787.                    (file-write-date fas-file)))
  788.            (push (module-name depend-mod) changed-modules))))
  789.          (when changed-modules
  790.            (apply #'format *query-io*
  791.                "~2&~#[Nothing has changed.~;~
  792. ~A has changed.~;~
  793. ~A and ~A have changed.~:;~
  794. ~A,~@{~<~%~4T~14:;~#[~; and~] ~A~>~^,~} have changed.~]~%"
  795.                changed-modules)
  796.            lsp-file))))))))
  797.  
  798. (defun check-for-recompilation (module file)
  799.   (setq module (module module))
  800.   (when (equal (pathname-type file) *fas-type*)
  801.     (dolist (depend-mod (module-total-dependencies module))
  802.       (let ((depend-file (find-module-file depend-mod nil t)))
  803.     (when (and depend-file
  804.            (> (file-write-date depend-file)
  805.               (file-write-date file)))
  806.       (warn "DM: compiled version of module ~S is being loaded
  807.     even though module ~S has been changed."
  808.         (module-name module)
  809.         (module-name depend-mod)))))))
  810.  
  811. (defvar *mload-verbose* t
  812.   "Controls whether MLOAD prints messages as it loads files.")
  813. (defvar *mload-print* nil
  814.   "Controls whether MLOAD passes :PRINT T to LOAD as it loads files.")
  815. (defvar *mload-depth* 0
  816.   "How many mloads are currently in progress?")
  817. (defvar *do-not-query*)
  818. (defvar *inside-loop* nil)
  819. (defvar *lisp-only* nil)
  820.  
  821. (defun mload (module &key (path nil) (force t) ((:print *mload-print*) *mload-print*) (query nil)
  822.              (compile nil) (complete nil) (trace nil)
  823.              ((:verbose *mload-verbose*) *mload-verbose*) (tag nil)
  824.              ((:lisp-only *lisp-only*) *lisp-only*))
  825.   #-(and lucid (not lcl4.0)) (declare (ignore trace))
  826.   (setq module (module module))
  827.   (when compile
  828.     (mcomp module :path path :load nil :query query :force nil :tag tag))
  829.   ;; The load time of a module is updated by PROVIDE-MODULE, but if the module
  830.   ;; isn't successfully loaded, set it back to what it was before loading.
  831.   (when (or force
  832.         (and complete (module-loading-p module))
  833.         (not (module-loading-p module)))
  834.     (let ((old-load-time (module-load-time module))
  835.       (old-module-dependencies (module-dependencies module))
  836.       (loaded-successfully nil)
  837.       (*current-module* module)
  838.       (*unwind-protect* nil))
  839.       (unwind-protect
  840.       (let* ((file (find-module-file module path *lisp-only*))
  841.          (file-write-date (file-write-date file)))
  842.         (when (and file
  843.                (or force
  844.                (and complete (module-loading-p module))
  845.                (> file-write-date (module-load-time module)))
  846.                (or (not query)
  847.                (ecase (query (if *inside-loop*
  848.                          '((:yes #\y #\space)
  849.                            (:no #\n #\rubout)
  850.                            (:all #\a)
  851.                            (:quit #\q))
  852.                        '((:yes #\y #\space)
  853.                          (:no #\n #\rubout)))
  854.                      "Load ~A?" (namestring file))
  855.                  (:yes t)
  856.                  (:no nil)
  857.                  (:all (setq *do-not-query* t) t)
  858.                  (:quit (when tag (throw tag nil)) nil))))
  859.           (check-for-recompilation module file)
  860.           (let ((*mload-depth* (1+ *mload-depth*))
  861.             (*modules-being-loaded*
  862.               (cons module
  863.                 (if complete
  864.                 (list (first *modules-being-loaded*))
  865.                   *modules-being-loaded*)))
  866.             (*module-provided-p* nil))
  867.         (when *mload-verbose*
  868.           (format t "~&;~V@TLoading ~A...~%"
  869.               *mload-depth* (enough-namestring file)))
  870.         (setf (module-file module) file)
  871.         (setf (module-dependencies module) ())
  872.                 (let ((*package* (find-package "ALLP")))
  873.           (load file :print *mload-print* :verbose nil
  874.             #+(and lucid (not lcl4.0)) :trace
  875.             #+(and lucid (not lcl4.0)) trace))
  876.         (setf (module-load-time module) file-write-date)
  877.         (when *mload-verbose*
  878.           (format t "~&;~V@TDone loading ~A~%"
  879.               *mload-depth* (enough-namestring file)))
  880.         (unless *module-provided-p*
  881.           (cerror "Ignore the discrepancy"
  882.               "The module ~A did not PROVIDE-MODULE itself"
  883.               (module-name module)))))
  884.         (setq loaded-successfully t))
  885.     (when *unwind-protect*
  886.       (funcall *unwind-protect*))
  887.     (unless loaded-successfully
  888.       (warn "Module ~S wasn't successfully loaded." (module-name module))
  889.       (setf (module-load-time module) old-load-time)
  890.       (setf (module-file module) nil)
  891.       (setf (module-dependencies module) old-module-dependencies)))))
  892.   (module-name module))
  893.  
  894. (defun mcomp (module &key (path nil) (load t) (force t) (query nil)
  895.                  (print nil) (verbose *load-verbose*) (tag nil)
  896.              (trace nil))
  897.   (declare (special *do-not-query*))
  898.   (setq module (module module))
  899.   (cond
  900.    ((module-compile-p module)
  901.     (let ((file (if force
  902.             (find-module-file module path t)
  903.           (module-needs-compiling-p module path))))
  904.       (cond
  905.        ((and file
  906.          (or (not query)
  907.          (ecase (query (if *inside-loop*
  908.                    '((:yes #\y #\space)
  909.                      (:no #\n #\rubout)
  910.                      (:all #\a)
  911.                      (:quit #\q))
  912.                  '((:yes #\y #\space)
  913.                    (:no #\n #\rubout)))
  914.                    "~A ~A?"
  915.                    (ecase (file-compilation-status file)
  916.                  ((:lsp-only :lsp-most-recent)
  917.                   "Compile")
  918.                  (:fas-most-recent
  919.                   "Recompile"))
  920.                    (namestring file))
  921.            (:yes t)
  922.            (:no nil)
  923.            (:all (setq *do-not-query* t) t)
  924.            (:quit (when tag (throw tag nil)) nil))))
  925.     (let ((*current-module* module)
  926.           (*unwind-protect* nil))
  927.       (unwind-protect
  928.           (dm-compile-file file)
  929.         (when *unwind-protect*
  930.           (funcall *unwind-protect*))))
  931.     (when load
  932.       (mload module :path path :compile nil :force t :print print
  933.          :verbose verbose :query nil :trace trace)))
  934.        (t
  935.     (when load
  936.       (mload module :path path :compile nil :force force :print print
  937.          :verbose verbose :query query :trace trace)))))
  938.     (module-name module))
  939.    (t
  940.     (if *current-module*
  941.     (warn 
  942. "Module ~S wanted to compile module ~S, which should never be compiled."
  943.       (module-name *current-module*)
  944.       (module-name module))
  945.       (warn
  946. "Someone wanted to compile module ~S, which should never be compiled."
  947.     (module-name module)))
  948.     nil)))
  949.  
  950. (defmacro mload-protect (&body body)
  951.   `(if (boundp '*unwind-protect*)
  952.        (setq *unwind-protect* #'(lambda () ,@body))
  953.      (warn "DM: MLOAD-PROTECT is being ignored because this module
  954.     is not being loaded by MLOAD.")))
  955.  
  956.  
  957. ;;;============================================================================
  958. ;;; Loading and compiling multiple modules
  959. ;;;============================================================================
  960.  
  961. (defun include-exclude (i e)
  962.   (cond ((null i)
  963.      (maphash #'(lambda (key value)
  964.               (declare (ignore key))
  965.               (push value i))
  966.           *module-table*))
  967.     ((not (consp i)) (setq i (list i))))
  968.   (unless (listp e) (setq e (list e)))
  969.   (set-difference (mapcar #'module i) (mapcar #'module e)))
  970.  
  971. (defun load-modules (&key (include nil) (exclude nil)
  972.               (compile nil)
  973.               (force nil) ((:lisp-only *lisp-only*) *lisp-only*)
  974.               (print nil) (verbose *load-verbose*)
  975.               (query nil) (trace nil))
  976.   (catch 'abort-loop
  977.     (let ((*inside-loop* t)
  978.       (*do-not-query* nil))
  979.       (declare (special *do-not-query*))
  980.       (dolist (mod (include-exclude include exclude))
  981.     (mload mod
  982.            :force force
  983.            :print print
  984.            :verbose verbose
  985.            :compile compile
  986.            :query (and query (not *do-not-query*))
  987.            :tag 'abort-loop
  988.            :trace trace
  989.            :lisp-only *lisp-only*)))))
  990.   
  991. (defun compile-modules (&key (include nil) (exclude nil)
  992.                  (load t) (force nil)
  993.                  (print nil) (verbose *load-verbose*)
  994.                  (query nil) (trace nil))
  995.   (catch 'abort-loop
  996.     (let ((*inside-loop* t)
  997.       (*do-not-query* nil))
  998.       (declare (special *do-not-query*))
  999.       (dolist (mod (include-exclude include exclude))
  1000.     (when (module-compile-p mod)
  1001.       (mcomp mod
  1002.          :load load
  1003.          :force force
  1004.          :print print
  1005.          :verbose verbose
  1006.          :query (and query (not *do-not-query*))
  1007.          :tag 'abort-loop
  1008.          :trace trace))))))
  1009.  
  1010.  
  1011. ;;;============================================================================
  1012. ;;; Providing and requiring modules
  1013. ;;;============================================================================
  1014.  
  1015. (defmacro provide-module (name &key (compile t) (documentation nil))
  1016.   `(progn
  1017.      (eval-when (eval load compile)
  1018.        (%define-module ,name ,compile ,documentation))
  1019.      (eval-when (eval)
  1020.        (%provide-module ,name 'eval))
  1021.      (eval-when (compile)
  1022.        (%provide-module ,name 'compile))
  1023.      (eval-when (load)
  1024.        (%provide-module ,name 'load))
  1025.      ,name))
  1026.  
  1027. (defun %define-module (module compile-p doc)
  1028.   (setq module (module module))
  1029.   (setf (module-compile-p module) compile-p)
  1030.   (setf (module-doc module) doc))
  1031.  
  1032. (defparameter *modules* nil)
  1033.  
  1034. (defun %provide-module (module type)
  1035.   (setq module (module module))
  1036.   (ecase type
  1037.     ((eval load)
  1038.      (pushnew (module-name module) *modules* :test #'string=)
  1039.      (cond ((eq module *current-module*)
  1040.         ;; It seems that we're being loaded by MLOAD, so we just
  1041.         ;; need to let MLOAD know that we've been loaded.
  1042.         (setq *module-provided-p* t))
  1043.        (t
  1044.         (when *current-module*
  1045.           (warn "The provide-module name ~S doesn't match
  1046. the require-module name ~S."
  1047.             (module-name module)
  1048.             (module-name *current-module*)))
  1049.         ;; We're not being loaded by MLOAD, so do the best job we
  1050.         ;; can to set the write date correctly.
  1051.         (setf (module-load-time module)
  1052.           (let ((file (find-module-file module nil nil nil)))
  1053.             (if file
  1054.             (file-write-date file)
  1055.               (get-universal-time)))))))
  1056.     (compile
  1057.      #-gclisp
  1058.      (unless (module-compile-p module)
  1059.        (warn "attempting to abort compilation of ~S" (module-name module))
  1060.        (throw :abort-compilation t))))
  1061.   module)
  1062.  
  1063. (defmacro require-module (module &rest keyword-args
  1064.                  &key path
  1065.                  (when t)
  1066.                  (eval :load)
  1067.                  (load :load)
  1068.                  (compile :load)
  1069.                  &allow-other-keys)
  1070.   (let ((keywords ()))
  1071.     (do* ((args keyword-args (cddr args))
  1072.       (keyword (first args) (first args))
  1073.       (value (second args) (second args)))
  1074.      ((endp args))
  1075.       (unless (member keyword '(:path :when :eval :load :compile))
  1076.     (if (valid-keyword-p keyword)
  1077.         (when value
  1078.           (pushnew keyword keywords))
  1079.       (warn "DM: Unknown keyword ~S ignored." keyword))))
  1080.     `(progn
  1081.        (eval-when (eval)
  1082.      (when ,when
  1083.        (%require-module ,module ,path ,eval 'eval ',keywords)))
  1084.        (eval-when (compile)
  1085.      (when ,when
  1086.        (%require-module ,module ,path ,compile 'compile ',keywords)))
  1087.        (eval-when (load)
  1088.      (when ,when
  1089.        (%require-module ,module ,path ,load 'load ',keywords)))
  1090.        ,module)))
  1091.  
  1092. (defun %require-module (module path action type keywords)
  1093.   (setq module (module module))
  1094.   (when *current-module*
  1095.     (if (incorporates-code-p keywords)
  1096.         (pushnew module (module-dependencies *current-module*))
  1097.       (setf (module-dependencies *current-module*)
  1098.       (delete module (module-dependencies *current-module*)))))
  1099.   (when action
  1100.     (mload module
  1101.        :path path
  1102.        :force nil
  1103.        :complete (requires-preloading-p keywords)
  1104.        :compile (and (eq type 'compile)
  1105.              (requires-precompiling-p keywords)))))
  1106.  
  1107. (defun unprovide-module (module)
  1108.   (setq module (module module))
  1109.   (remhash (module-name module) *module-table*)
  1110.   (setq *modules* (delete (module-name module) *modules* :test #'string=))
  1111.   (module-name module))
  1112.